#include "virtual_device.h"

#define DEV_MAJOR 196
#define MINORBITS 20
#define DEV_NAME "virtual-device"
#define MINOR_ALLOCED ((void *)-1)

static DEFINE_IDR(device_minor_idr);

static int alloc_minor(int *minor)
{
	int ret;

	idr_preload(GFP_KERNEL);
	ret = idr_alloc(&device_minor_idr, MINOR_ALLOCED, 0, 1 << MINORBITS, GFP_NOWAIT);
	idr_preload_end();
	if (ret < 0) {
		return ret;
	}
	*minor = ret;
	return 0;
}

static int create_gendisk(virtual_device_t *device)
{
	int ret = -1;
	int minor = -1;
	struct gendisk *disk = NULL;

	ret = register_blkdev(DEV_MAJOR, DEV_NAME);
	if (ret < 0) {
		return -1;
	}

	ret = alloc_minor(&minor);
	if (ret < 0) {
		return -1;
	}

	disk = blk_alloc_disk(NUMA_NO_NODE);
	if (disk == NULL) {
		idr_remove(&device_minor_idr, minor);
		return -1;
	}
	disk->major = DEV_MAJOR;
	disk->minors = 1;
	disk->first_minor = minor;
	disk->flags |= GENHD_FL_NO_PART;
	disk->fops = device->dev_block->bd_disk->fops;
	disk->private_data = device->dev_block->bd_disk->private_data;
    disk->part0->bd_start_sect = device->dev_block->bd_start_sect;
    disk->part0->bd_nr_sectors = device->dev_block->bd_nr_sectors;
	sprintf(disk->disk_name, "%s-%d", DEV_NAME, minor);
	device->disk = disk;
	device->dev_queue = disk->queue;
	return 0;
}

static void delete_gendisk(virtual_device_t *device)
{
	del_gendisk(device->disk);
	idr_remove(&device_minor_idr, device->minor);
	unregister_blkdev(DEV_MAJOR, DEV_NAME);
}


static struct blk_mq_ops device_mq_ops = {0};

int init_device_queue(virtual_device_t *device)
{
	int err = -1;
    struct gendisk *disk = NULL;
    struct request_queue *q = NULL;
    struct blk_mq_tag_set *set = NULL;
    const struct blk_mq_ops *ops = NULL;
    disk = device->dev_block->bd_disk;
    q = disk->queue;
    set = &device->dev_tag_set;
    if (set == NULL) {
        printk("set == NULL\n");
        return 0;
    }

    ops = q->tag_set->ops;
    device_mq_ops.queue_rq = ops->queue_rq;
    device_mq_ops.complete = ops->complete;
    device_mq_ops.init_request = ops->init_request;
    memset(&device->dev_tag_set, 0, sizeof(struct blk_mq_tag_set));
	device->dev_tag_set.ops = &device_mq_ops;
	device->dev_tag_set.queue_depth = q->tag_set->queue_depth;
	device->dev_tag_set.numa_node = q->tag_set->numa_node;
	device->dev_tag_set.flags = q->tag_set->flags;
	device->dev_tag_set.nr_hw_queues = q->tag_set->nr_hw_queues;
	device->dev_tag_set.driver_data = q->tag_set->driver_data;
	device->dev_tag_set.cmd_size = q->tag_set->cmd_size;	
	err = blk_mq_alloc_tag_set(&device->dev_tag_set);
	if (err) {
		return -1;
	}
	err = blk_mq_init_allocated_queue(&device->dev_tag_set, device->dev_queue);
	if (err) {
		blk_mq_free_tag_set(&device->dev_tag_set);
		return -1;
	}
    printk("blk_mq_init_allocated_queue\n");
    return 0;
}

void exit_device_queue(virtual_device_t *device)
{
    return;
}

virtual_device_t *create_virtual_device(struct block_device *dev_block)
{
	int ret = -1;
	virtual_device_t *device = NULL;

	device = kmalloc(sizeof(virtual_device_t), GFP_KERNEL);
	if (device == NULL) {
		return NULL;
	}
	device->dev_block = dev_block;

	ret = create_gendisk(device);
	if (ret == -1) {
		kfree(device);
		return NULL;
	}
    printk("create_gendisk\n");
	ret = init_device_queue(device);
	if (ret == -1) {
		delete_gendisk(device);
		kfree(device);
		return NULL;
	}
    printk("init_device_queue\n");
	ret = add_disk(device->disk);
	if (ret) {
		exit_device_queue(device);
		delete_gendisk(device);
		kfree(device);
		return NULL;
	}
    printk("add_disk\n");
	return device;
}

int delete_virtual_device(virtual_device_t *device)
{
	exit_device_queue(device);
	delete_gendisk(device);
	kfree(device);
	return 0;
}
